home *** CD-ROM | disk | FTP | other *** search
/ PC World 2008 March / PCWorld_2008-03_cd.bin / v cisle / mobiDVD / MobiDVD-1.0.0.6.exe / xulrunner / components / storage-Legacy.js < prev    next >
Text File  |  2007-07-11  |  27KB  |  828 lines

  1. /* ***** BEGIN LICENSE BLOCK *****
  2.  * Version: MPL 1.1/GPL 2.0/LGPL 2.1
  3.  *
  4.  * The contents of this file are subject to the Mozilla Public License Version
  5.  * 1.1 (the "License"); you may not use this file except in compliance with
  6.  * the License. You may obtain a copy of the License at
  7.  * http://www.mozilla.org/MPL/
  8.  *
  9.  * Software distributed under the License is distributed on an "AS IS" basis,
  10.  * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
  11.  * for the specific language governing rights and limitations under the
  12.  * License.
  13.  *
  14.  * The Original Code is mozilla.org code.
  15.  *
  16.  * The Initial Developer of the Original Code is Mozilla Corporation.
  17.  * Portions created by the Initial Developer are Copyright (C) 2007
  18.  * the Initial Developer. All Rights Reserved.
  19.  *
  20.  * Contributor(s):
  21.  *  Justin Dolske <dolske@mozilla.com> (original author)
  22.  *
  23.  * Alternatively, the contents of this file may be used under the terms of
  24.  * either the GNU General Public License Version 2 or later (the "GPL"), or
  25.  * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
  26.  * in which case the provisions of the GPL or the LGPL are applicable instead
  27.  * of those above. If you wish to allow use of your version of this file only
  28.  * under the terms of either the GPL or the LGPL, and not to allow others to
  29.  * use your version of this file under the terms of the MPL, indicate your
  30.  * decision by deleting the provisions above and replace them with the notice
  31.  * and other provisions required by the GPL or the LGPL. If you do not delete
  32.  * the provisions above, a recipient may use your version of this file under
  33.  * the terms of any one of the MPL, the GPL or the LGPL.
  34.  *
  35.  * ***** END LICENSE BLOCK ***** */
  36.  
  37.  
  38. const Cc = Components.classes;
  39. const Ci = Components.interfaces;
  40.  
  41. Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
  42.  
  43. function LoginManagerStorage_legacy() { };
  44.  
  45. LoginManagerStorage_legacy.prototype = {
  46.  
  47.     classDescription  : "LoginManagerStorage_legacy",
  48.     contractID : "@mozilla.org/login-manager/storage/legacy;1",
  49.     classID : Components.ID("{e09e4ca6-276b-4bb4-8b71-0635a3a2a007}"),
  50.     QueryInterface : XPCOMUtils.generateQI([Ci.nsILoginManagerStorage]),
  51.  
  52.     __logService : null, // Console logging service, used for debugging.
  53.     get _logService() {
  54.         if (!this.__logService)
  55.             this.__logService = Cc["@mozilla.org/consoleservice;1"].
  56.                                 getService(Ci.nsIConsoleService);
  57.         return this.__logService;
  58.     },
  59.  
  60.     __decoderRing : null,  // nsSecretDecoderRing service
  61.     get _decoderRing() {
  62.         if (!this.__decoderRing)
  63.             this.__decoderRing = Cc["@mozilla.org/security/sdr;1"].
  64.                                  getService(Ci.nsISecretDecoderRing);
  65.         return this.__decoderRing;
  66.     },
  67.  
  68.     _prefBranch : null,  // Preferences service
  69.  
  70.     _signonsFile : null,  // nsIFile for "signons2.txt" (or whatever pref is)
  71.     _debug       : false, // mirrors signon.debug
  72.  
  73.  
  74.     /*
  75.      * Core datastructures
  76.      *
  77.      * EG: _logins["http://site.com"][0].password
  78.      * EG: _disabledHosts["never.site.com"]
  79.      */
  80.     _logins        : null, 
  81.     _disabledHosts : null,
  82.  
  83.  
  84.     /*
  85.      * log
  86.      *
  87.      * Internal function for logging debug messages to the Error Console.
  88.      */
  89.     log : function (message) {
  90.         if (!this._debug)
  91.             return;
  92.         dump("PwMgr Storage: " + message + "\n");
  93.         this._logService.logStringMessage("PwMgr Storage: " + message);
  94.     },
  95.  
  96.  
  97.  
  98.  
  99.     /* ==================== Public Methods ==================== */
  100.  
  101.  
  102.  
  103.  
  104.     initWithFile : function(aInputFile, aOutputFile) {
  105.         this._signonsFile = aInputFile;
  106.  
  107.         this.init();
  108.  
  109.         if (aOutputFile) {
  110.             this._signonsFile = aOutputFile;
  111.             this._writeFile();
  112.         }
  113.     },
  114.  
  115.     /*
  116.      * init
  117.      *
  118.      * Initialize this storage component and load stored passwords from disk.
  119.      */
  120.     init : function () {
  121.         this._logins  = {};
  122.         this._disabledHosts = {};
  123.  
  124.         // Connect to the correct preferences branch.
  125.         this._prefBranch = Cc["@mozilla.org/preferences-service;1"]
  126.                                 .getService(Ci.nsIPrefService);
  127.         this._prefBranch = this._prefBranch.getBranch("signon.");
  128.         this._prefBranch.QueryInterface(Ci.nsIPrefBranch2);
  129.  
  130.         this._debug = this._prefBranch.getBoolPref("debug");
  131.  
  132.         // Check to see if the internal PKCS#11 token has been initialized.
  133.         // If not, set a blank password.
  134.         var tokenDB = Cc["@mozilla.org/security/pk11tokendb;1"]
  135.                             .getService(Ci.nsIPK11TokenDB);
  136.  
  137.         var token = tokenDB.getInternalKeyToken();
  138.         if (token.needsUserInit) {
  139.             this.log("Initializing key3.db with default blank password.");
  140.             token.initPassword("");
  141.         }
  142.  
  143.         var importFile = null;
  144.         // If initWithFile is calling us, _signonsFile is already set.
  145.         if (!this._signonsFile)
  146.             [this._signonsFile, importFile] = this._getSignonsFile();
  147.  
  148.         // If we have an import file, do a switcharoo before reading it.
  149.         if (importFile) {
  150.             this.log("Importing " + importFile.path);
  151.  
  152.             var tmp = this._signonsFile;
  153.             this._signonsFile = importFile;
  154.         }
  155.  
  156.         // Read in the stored login data.
  157.         this._readFile()
  158.  
  159.         // If we were importing, write back to the normal file.
  160.         if (importFile) {
  161.             this._signonsFile = tmp;
  162.             this._writeFile();
  163.         }
  164.     },
  165.  
  166.  
  167.     /*
  168.      * addLogin
  169.      *
  170.      */
  171.     addLogin : function (login) {
  172.         // We rely on using login.wrappedJSObject. addLogin is the
  173.         // only entry point where we might get a nsLoginInfo object
  174.         // that wasn't created by us (and so might not be a JS
  175.         // implementation being wrapped)
  176.         if (!login.wrappedJSObject) {
  177.             var clone = Cc["@mozilla.org/login-manager/loginInfo;1"].
  178.                         createInstance(Ci.nsILoginInfo);
  179.             clone.init(login.hostname, login.formSubmitURL, login.httpRealm,
  180.                        login.username,      login.password,
  181.                        login.usernameField, login.passwordField);
  182.             login = clone;
  183.         }
  184.  
  185.         var key = login.hostname;
  186.  
  187.         // If first entry for key, create an Array to hold it's logins.
  188.         if (!this._logins[key])
  189.             this._logins[key] = [];
  190.  
  191.         this._logins[key].push(login);
  192.  
  193.         this._writeFile();
  194.     },
  195.  
  196.  
  197.     /*
  198.      * removeLogin
  199.      *
  200.      */
  201.     removeLogin : function (login) {
  202.         var key = login.hostname;
  203.         var logins = this._logins[key];
  204.  
  205.         if (!logins)
  206.             throw "No logins found for hostname (" + key + ")";
  207.  
  208.         // The specified login isn't encrypted, so we need to ensure
  209.         // the logins we're comparing with are decrypted.
  210.         this._decryptLogins(logins);
  211.  
  212.         for (var i = 0; i < logins.length; i++) {
  213.             if (logins[i].equals(login)) {
  214.                 logins.splice(i, 1); // delete that login from array.
  215.                 break;
  216.                 // Note that if there are duplicate entries, they'll
  217.                 // have to be deleted one-by-one.
  218.             }
  219.         }
  220.  
  221.         // Did we delete all the logins for this host?
  222.         if (logins.length == 0)
  223.             delete this._logins[key];
  224.  
  225.         this._writeFile();
  226.     },
  227.  
  228.  
  229.     /*
  230.      * modifyLogin
  231.      *
  232.      */
  233.     modifyLogin : function (oldLogin, newLogin) {
  234.         this.removeLogin(oldLogin);
  235.         this.addLogin(newLogin);
  236.     },
  237.  
  238.  
  239.     /*
  240.      * getAllLogins
  241.      *
  242.      * Returns an array of nsAccountInfo.
  243.      */
  244.     getAllLogins : function (count) {
  245.         var result = [], userCanceled;
  246.  
  247.         // Each entry is an array -- append the array entries to |result|.
  248.         for each (var hostLogins in this._logins) {
  249.             result = result.concat(hostLogins);
  250.         }
  251.  
  252.         // decrypt entries for caller.
  253.         [result, userCanceled] = this._decryptLogins(result);
  254.  
  255.         count.value = result.length; // needed for XPCOM
  256.         return result;
  257.     },
  258.  
  259.  
  260.     /*
  261.      * removeAllLogins
  262.      *
  263.      * Removes all logins from storage.
  264.      */
  265.     removeAllLogins : function () {
  266.         this._logins = {};
  267.         // Disabled hosts kept, as one presumably doesn't want to erase those.
  268.  
  269.         this._writeFile();
  270.     },
  271.  
  272.  
  273.     /*
  274.      * getAllDisabledHosts
  275.      *
  276.      */
  277.     getAllDisabledHosts : function (count) {
  278.         var result = [];
  279.  
  280.         for (var hostname in this._disabledHosts) {
  281.             result.push(hostname);
  282.         }
  283.  
  284.         count.value = result.length; // needed for XPCOM
  285.         return result;
  286.     },
  287.  
  288.  
  289.     /*
  290.      * getLoginSavingEnabled
  291.      *
  292.      */
  293.     getLoginSavingEnabled : function (hostname) {
  294.         return !this._disabledHosts[hostname];
  295.     },
  296.  
  297.  
  298.     /*
  299.      * setLoginSavingEnabled
  300.      *
  301.      */
  302.     setLoginSavingEnabled : function (hostname, enabled) {
  303.         if (enabled)
  304.             delete this._disabledHosts[hostname];
  305.         else
  306.             this._disabledHosts[hostname] = true;
  307.  
  308.         this._writeFile();
  309.     },
  310.  
  311.  
  312.     /*
  313.      * findLogins
  314.      *
  315.      */
  316.     findLogins : function (count, hostname, formSubmitURL, httpRealm) {
  317.         var hostLogins = this._logins[hostname];
  318.         if (hostLogins == null) {
  319.             count.value = 0;
  320.             return [];
  321.         }
  322.  
  323.         var result = [], userCanceled;
  324.  
  325.         for each (var login in hostLogins) {
  326.  
  327.             // If looking for an HTTP login, make sure the httpRealms match.
  328.             if (httpRealm != login.httpRealm)
  329.                 continue;
  330.  
  331.             // If looking for a form login, make sure the action URLs match 
  332.             // ...unless the stored login is blank (not null), which means
  333.             // login was stored before we started keeping the action URL.
  334.             if (formSubmitURL != login.formSubmitURL &&
  335.                 login.formSubmitURL != "")
  336.                 continue;
  337.  
  338.             result.push(login);
  339.         }
  340.  
  341.         // Decrypt entries found for the caller.
  342.         [result, userCanceled] = this._decryptLogins(result);
  343.  
  344.         // We want to throw in this case, so that the Login Manager
  345.         // knows to stop processing forms on the page so the user isn't
  346.         // prompted multiple times.
  347.         if (userCanceled)
  348.             throw "User canceled Master Password entry";
  349.  
  350.         count.value = result.length; // needed for XPCOM
  351.         return result;
  352.     },
  353.  
  354.  
  355.  
  356.  
  357.     /* ==================== Internal Methods ==================== */
  358.  
  359.  
  360.  
  361.  
  362.     /*
  363.      * _getSignonsFile
  364.      *
  365.      * Determines what file to use based on prefs. Returns it as a
  366.      * nsILocalFile, along with a file to import from first (if needed)
  367.      *
  368.      */
  369.     _getSignonsFile : function() {
  370.         var importFile = null;
  371.  
  372.         // Get the location of the user's profile.
  373.         var DIR_SERVICE = new Components.Constructor(
  374.                 "@mozilla.org/file/directory_service;1", "nsIProperties");
  375.         var pathname = (new DIR_SERVICE()).get("ProfD", Ci.nsIFile).path;
  376.  
  377.  
  378.         // First try the default pref...
  379.         var filename = this._prefBranch.getCharPref("SignonFileName2");
  380.  
  381.         var file = Cc["@mozilla.org/file/local;1"].
  382.                    createInstance(Ci.nsILocalFile);
  383.         file.initWithPath(pathname);
  384.         file.append(filename);
  385.  
  386.         if (!file.exists()) {
  387.             this.log("SignonFilename2 file does not exist. file=" +
  388.                      filename + ", path=" + pathname);
  389.  
  390.             // Then try the old pref...
  391.             var oldname = this._prefBranch.getCharPref("SignonFileName");
  392.  
  393.             importFile = Cc["@mozilla.org/file/local;1"].
  394.                          createInstance(Ci.nsILocalFile);
  395.             importFile.initWithPath(pathname);
  396.             importFile.append(oldname);
  397.  
  398.             if (!importFile.exists()) {
  399.                 this.log("SignonFilename1 file does not exist. file=" +
  400.                         oldname + ", path=" + pathname);
  401.                 importFile = null;
  402.             }
  403.         }
  404.  
  405.         return [file, importFile];
  406.     },
  407.  
  408.  
  409.     /*
  410.      * _readFile
  411.      *
  412.      */
  413.     _readFile : function () {
  414.         var oldFormat = false;
  415.  
  416.         this.log("Reading passwords from " + this._signonsFile.path);
  417.  
  418.         // If it doesn't exist, just create an empty file and bail out.
  419.         if (!this._signonsFile.exists()) {
  420.             this.log("Creating new signons file...");
  421.             this._writeFile();
  422.             return;
  423.         }
  424.  
  425.         var inputStream = Cc["@mozilla.org/network/file-input-stream;1"]
  426.                                 .createInstance(Ci.nsIFileInputStream);
  427.         // init the stream as RD_ONLY, -1 == default permissions.
  428.         inputStream.init(this._signonsFile, 0x01, -1, null);
  429.         var lineStream = inputStream.QueryInterface(Ci.nsILineInputStream);
  430.         var line = { value: "" };
  431.  
  432.         const STATE = { HEADER : 0, REJECT : 1, REALM : 2,
  433.                         USERFIELD : 3, USERVALUE : 4,
  434.                         PASSFIELD : 5, PASSVALUE : 6, ACTIONURL : 7 };
  435.         var parseState = STATE.HEADER;
  436.  
  437.         var nsLoginInfo = new Components.Constructor(
  438.                 "@mozilla.org/login-manager/loginInfo;1", Ci.nsILoginInfo);
  439.         var processEntry = false;
  440.  
  441.         do {
  442.             var hasMore = lineStream.readLine(line);
  443.  
  444.             switch (parseState) {
  445.                 // Check file header
  446.                 case STATE.HEADER:
  447.                     if (line.value == "#2c") {
  448.                         oldFormat = true;
  449.                     } else if (line.value != "#2d") {
  450.                         this.log("invalid file header (" + line.value + ")");
  451.                         throw "invalid file header in signons file";
  452.                         // We could disable later writing to file, so we
  453.                         // don't clobber whatever it is. ...however, that
  454.                         // would mean corrupt files are not self-healing.
  455.                         return;
  456.                     }
  457.                     parseState++;
  458.                     break;
  459.  
  460.                 // Line is a hostname for which passwords should never be saved.
  461.                 case STATE.REJECT:
  462.                     if (line.value == ".") {
  463.                         parseState++;
  464.                         break;
  465.                     }
  466.  
  467.                     this._disabledHosts[line.value] = true;
  468.  
  469.                     break;
  470.  
  471.                 // Line is a hostname, saved login(s) will follow
  472.                 case STATE.REALM:
  473.                     var hostrealm = line.value;
  474.  
  475.                     // Format is "http://site.com", with "(some realm)"
  476.                     // appended if it's a HTTP-Auth login.
  477.                     const realmFormat = /^(.+?)( \(.*\))?$/; // XXX .* or .+?
  478.                     var matches = realmFormat.exec(hostrealm);
  479.  
  480.                     var hostname, httpRealm;
  481.                     if (matches && matches.length == 3) {
  482.                         hostname  = matches[1];
  483.                         httpRealm = matches[2] ?
  484.                                         matches[2].slice(2, -1) : null;
  485.                     } else {
  486.                         if (hostrealm != "") {
  487.                             // Uhoh. This shouldn't happen, but try to deal.
  488.                             this.log("Error parsing host/realm: " + hostrealm);
  489.                         }
  490.                         hostname = hostrealm;
  491.                         httpRealm = null;
  492.                     }
  493.  
  494.                     parseState++;
  495.                     break;
  496.  
  497.                 // Line is the HTML 'name' attribute for the username field
  498.                 // (or "." to indicate end of hostrealm)
  499.                 case STATE.USERFIELD:
  500.                     if (line.value == ".") {
  501.                         parseState = STATE.REALM;
  502.                         break;
  503.                     }
  504.  
  505.                     var entry = new nsLoginInfo();
  506.                     entry.hostname  = hostname;
  507.                     entry.httpRealm = httpRealm;
  508.  
  509.                     entry.usernameField = line.value;
  510.                     parseState++;
  511.                     break;
  512.  
  513.                 // Line is a username
  514.                 case STATE.USERVALUE:
  515.                     entry.wrappedJSObject.encryptedUsername = line.value;
  516.                     parseState++;
  517.                     break;
  518.  
  519.                 // Line is the HTML 'name' attribute for the password field,
  520.                 // with a leading '*' character
  521.                 case STATE.PASSFIELD:
  522.                     entry.passwordField = line.value.substr(1);
  523.                     parseState++;
  524.                     break;
  525.  
  526.                 // Line is a password
  527.                 case STATE.PASSVALUE:
  528.                     entry.wrappedJSObject.encryptedPassword = line.value;
  529.                     if (oldFormat) {
  530.                         entry.formSubmitURL = "";
  531.                         processEntry = true;
  532.                         parseState = STATE.USERFIELD;
  533.                     } else {
  534.                         parseState++;
  535.                     }
  536.                     break;
  537.  
  538.                 // Line is the action URL
  539.                 case STATE.ACTIONURL:
  540.                     entry.formSubmitURL = line.value;
  541.                     processEntry = true;
  542.                     parseState = STATE.USERFIELD;
  543.                     break;
  544.  
  545.             }
  546.  
  547.             if (processEntry) {
  548.                 if (!this._logins[hostname])
  549.                     this._logins[hostname] = [];
  550.  
  551.                 this._logins[hostname].push(entry);
  552.  
  553.                 entry = null;
  554.                 processEntry = false;
  555.             }
  556.         } while (hasMore);
  557.  
  558.         lineStream.close();
  559.  
  560.         return;
  561.     },
  562.  
  563.  
  564.     /*
  565.      * _writeFile
  566.      *
  567.      */
  568.     _writeFile : function () {
  569.         function writeLine(data) {
  570.             data += "\r\n";
  571.             outputStream.write(data, data.length);
  572.         }
  573.  
  574.         this.log("Writing passwords to " + this._signonsFile.path);
  575.  
  576.         var outputStream = Cc["@mozilla.org/network/safe-file-output-stream;1"]
  577.                                 .createInstance(Ci.nsIFileOutputStream);
  578.         outputStream.QueryInterface(Ci.nsISafeOutputStream);
  579.  
  580.         // WR_ONLY|CREAT|TRUNC
  581.         outputStream.init(this._signonsFile, 0x02 | 0x08 | 0x20, 0600, null);
  582.  
  583.         // write file version header
  584.         writeLine("#2d");
  585.  
  586.         // write disabled logins list
  587.         for (var hostname in this._disabledHosts) {
  588.             writeLine(hostname);
  589.         }
  590.  
  591.         // write end-of-reject-list marker
  592.         writeLine(".");
  593.  
  594.         for (var hostname in this._logins) {
  595.             function sortByRealm(a,b) {
  596.                 a = a.httpRealm;
  597.                 b = b.httpRealm;
  598.  
  599.                 if (!a && !b)
  600.                     return  0;
  601.  
  602.                 if (!a || a < b)
  603.                     return -1;
  604.  
  605.                 if (!b || b > a)
  606.                     return  1;
  607.  
  608.                 return 0; // a==b, neither is null
  609.             }
  610.  
  611.             // Sort logins by httpRealm. This allows us to group multiple
  612.             // logins for the same realm together.
  613.             this._logins[hostname].sort(sortByRealm);
  614.  
  615.  
  616.             // write each login known for the host
  617.             var lastRealm = null;
  618.             var firstEntry = true;
  619.             var userCanceled = false;
  620.             for each (var login in this._logins[hostname]) {
  621.  
  622.                 // If this login is for a new realm, start a new entry.
  623.                 if (login.httpRealm != lastRealm || firstEntry) {
  624.                     // end previous entry, if needed.
  625.                     if (!firstEntry)
  626.                         writeLine(".");
  627.  
  628.                     var hostrealm = login.hostname;
  629.                     if (login.httpRealm)
  630.                         hostrealm += " (" + login.httpRealm + ")";
  631.  
  632.                     writeLine(hostrealm);
  633.                 }
  634.  
  635.                 firstEntry = false;
  636.  
  637.                 // Get the encrypted value of the username. Newly added
  638.                 // logins will need the plaintext value encrypted.
  639.                 var encUsername = login.wrappedJSObject.encryptedUsername;
  640.                 if (!encUsername) {
  641.                     [encUsername, userCanceled] = this._encrypt(login.username);
  642.                     login.wrappedJSObject.encryptedUsername = encUsername;
  643.                 }
  644.  
  645.                 if (userCanceled)
  646.                     break;
  647.  
  648.                 // Get the encrypted value of the password. Newly added
  649.                 // logins will need the plaintext value encrypted.
  650.                 var encPassword = login.wrappedJSObject.encryptedPassword;
  651.                 if (!encPassword) {
  652.                     [encPassword, userCanceled] = this._encrypt(login.password);
  653.                     login.wrappedJSObject.encryptedPassword = encPassword;
  654.                 }
  655.  
  656.                 if (userCanceled)
  657.                     break;
  658.  
  659.  
  660.                 writeLine((login.usernameField ?  login.usernameField : ""));
  661.                 writeLine(encUsername);
  662.                 writeLine("*" +
  663.                     (login.passwordField ?  login.passwordField : ""));
  664.                 writeLine(encPassword);
  665.                 writeLine((login.formSubmitURL ? login.formSubmitURL : ""));
  666.  
  667.                 lastRealm = login.httpRealm;
  668.             }
  669.  
  670.             if (userCanceled) {
  671.                 this.log("User canceled Master Password, aborting write.");
  672.                 // .close will cause an abort w/o modifying original file
  673.                 outputStream.close();
  674.                 return;
  675.             }
  676.  
  677.             // write end-of-host marker
  678.             writeLine(".");
  679.         }
  680.  
  681.         // [if there were no hosts, no end-of-host marker (".") needed]
  682.  
  683.         outputStream.finish();
  684.     },
  685.  
  686.  
  687.     /*
  688.      * _decryptLogins
  689.      *
  690.      * Decrypts username and password fields in the provided array of
  691.      * logins. This is deferred from the _readFile() code, so that
  692.      * the user is not prompted for a master password (if set) until
  693.      * the entries are actually used.
  694.      *
  695.      * The entries specified by the array will be decrypted, if possible.
  696.      * An array of successfully decrypted logins will be returned. The return
  697.      * value should be given to external callers (since still-encrypted
  698.      * entries are useless), whereas internal callers generally don't want
  699.      * to lose unencrypted entries (eg, because the user clicked Cancel
  700.      * instead of entering their master password)
  701.      */
  702.     _decryptLogins : function (logins) {
  703.         var result = [], userCanceled = false;
  704.  
  705.         for each (var login in logins) {
  706.             if (!login.username)
  707.                 [login.username, userCanceled] =
  708.                     this._decrypt(login.wrappedJSObject.encryptedUsername);
  709.  
  710.             if (userCanceled)
  711.                 break;
  712.  
  713.             if (!login.password)
  714.                 [login.password, userCanceled] =
  715.                     this._decrypt(login.wrappedJSObject.encryptedPassword);
  716.  
  717.             // Probably can't hit this case, but for completeness...
  718.             if (userCanceled)
  719.                 break;
  720.  
  721.             // If decryption failed (corrupt entry?) skip it.
  722.             // XXX remove it from the original list entirely?
  723.             if (!login.username || !login.password)
  724.                 continue;
  725.  
  726.             // Force any old mime64-obscured entries to be reencrypted.
  727.             if (login.wrappedJSObject.encryptedUsername &&
  728.                 login.wrappedJSObject.encryptedUsername.charAt(0) == '~')
  729.                 login.wrappedJSObject.encryptedUsername = null;
  730.  
  731.             if (login.wrappedJSObject.encryptedPassword &&
  732.                 login.wrappedJSObject.encryptedPassword.charAt(0) == '~')
  733.                 login.wrappedJSObject.encryptedPassword = null;
  734.  
  735.             result.push(login);
  736.         }
  737.  
  738.         return [result, userCanceled];
  739.     },
  740.  
  741.  
  742.     /*
  743.      * _encrypt
  744.      *
  745.      * Encrypts the specified string, using the SecretDecoderRing.
  746.      *
  747.      * Returns [cipherText, userCanceled] where:
  748.      *  cipherText   -- the encrypted string, or null if it failed.
  749.      *  userCanceled -- if the encryption failed, this is true if the
  750.      *                  user selected Cancel when prompted to enter their
  751.      *                  Master Password. The caller should bail out, and not
  752.      *                  not request that more things be encrypted (which 
  753.      *                  results in prompting the user for a Master Password
  754.      *                  over and over.)
  755.      */
  756.     _encrypt : function (plainText) {
  757.         var cipherText = null, userCanceled = false;
  758.  
  759.         try {
  760.             var converter = Cc["@mozilla.org/intl/scriptableunicodeconverter"].
  761.                             createInstance(Ci.nsIScriptableUnicodeConverter);
  762.             converter.charset = "UTF-8";
  763.             var plainOctet = converter.ConvertFromUnicode(plainText);
  764.             plainOctet += converter.Finish();
  765.             cipherText = this._decoderRing.encryptString(plainOctet);
  766.         } catch (e) {
  767.             this.log("Failed to encrypt string. (" + e.name + ")");
  768.             if (e.result == Components.results.NS_ERROR_NOT_AVAILABLE)
  769.                 userCanceled = true;
  770.         }
  771.  
  772.         return [cipherText, userCanceled];
  773.     },
  774.  
  775.  
  776.     /*
  777.      * _decrypt
  778.      *
  779.      * Decrypts the specified string, using the SecretDecoderRing.
  780.      *
  781.      * Returns [plainText, userCanceled] where:
  782.      *  plainText    -- the decrypted string, or null if it failed.
  783.      *  userCanceled -- if the decryption failed, this is true if the
  784.      *                  user selected Cancel when prompted to enter their
  785.      *                  Master Password. The caller should bail out, and not
  786.      *                  not request that more things be decrypted (which 
  787.      *                  results in prompting the user for a Master Password
  788.      *                  over and over.)
  789.      */
  790.     _decrypt : function (cipherText) {
  791.         var plainText = null, userCanceled = false;
  792.  
  793.         try {
  794.             var plainOctet;
  795.             if (cipherText.charAt(0) == '~') {
  796.                 // The older file format obscured entries by
  797.                 // base64-encoding them. These entries are signaled by a
  798.                 // leading '~' character. 
  799.                 plainOctet = atob(cipherText.substring(1));
  800.             } else {
  801.                 plainOctet = this._decoderRing.decryptString(cipherText);
  802.             }
  803.             var converter = Cc["@mozilla.org/intl/scriptableunicodeconverter"]
  804.                               .createInstance(Ci.nsIScriptableUnicodeConverter);
  805.             converter.charset = "UTF-8";
  806.             plainText = converter.ConvertToUnicode(plainOctet);
  807.         } catch (e) {
  808.             this.log("Failed to decrypt string: " + cipherText +
  809.                 " (" + e.name + ")");
  810.  
  811.             // If the user clicks Cancel, we get NS_ERROR_NOT_AVAILABLE.
  812.             // If the cipherText is bad / wrong key, we get NS_ERROR_FAILURE
  813.             // Wrong passwords are handled by the decoderRing reprompting;
  814.             // we get no notification.
  815.             if (e.result == Components.results.NS_ERROR_NOT_AVAILABLE)
  816.                 userCanceled = true;
  817.         }
  818.  
  819.         return [plainText, userCanceled];
  820.     },
  821.  
  822. }; // end of nsLoginManagerStorage_legacy implementation
  823.  
  824. var component = [LoginManagerStorage_legacy];
  825. function NSGetModule(compMgr, fileSpec) {
  826.     return XPCOMUtils.generateModule(component);
  827. }
  828.